Casadesign - DRAFT DO NOT MERGE#7051
Draft
giacoelho wants to merge 202 commits into
Draft
Conversation
Introduce Tailwind v4 alongside the existing Bootstrap stack so the UI can be modernized page-by-page without breaking current views. - Add tailwindcss + @tailwindcss/cli (v4), build:tailwind[:dev] npm scripts, and a `tw` process in Procfile.dev. Output lands in app/assets/builds, which Sprockets already serves via the manifest's link_directory. - Add app/assets/stylesheets/tailwind.css: CSS-first design system (Inter font + an indigo "brand" palette) — the visual direction for the redesign. - New Tailwind-only layout casa_auth.html.erb (split brand panel + form), used by both the user and all-casa sign-in controllers. - Redesign devise/sessions/new.html.erb in a clean, modern Stripe/Airbnb style and wire up remember-me properly. Scoped to sign-in only; every other page keeps the Bootstrap `application` layout untouched. First step of an incremental migration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Approved font direction: swap the design-system typeface and the auth layout's webfont link from Inter to Figtree (warmer humanist sans). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- design.md: living design-system doc (Tailwind v4 approach, Figtree type scale, color tokens, icon-tile pattern, component + empty-state specs, app-shell spec, migration checklist). Codifies the icon-tile treatment (icons on a soft colored background) for stat/status icons and reserves initial-avatars for people only. - CLAUDE.md: point to design.md and record the casadesign commit/push-per-checkpoint workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New Tailwind-only layout: left sidebar with Pundit-guarded nav mirroring the existing routes, plus a top bar (notifications, profile menu, sign out). Coexists with the Bootstrap `application` layout — redesigned authenticated pages opt in via `layout "casa_app"`. Icons via Bootstrap Icons CDN + Figtree (per design.md). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Supervisors now land on a real dashboard (DashboardController#show renders it in the casa_app shell) instead of the Bootstrap volunteers table: - SupervisorDashboard service builds per-volunteer follow-up status + summary stats from the supervisor's assigned volunteers. - View: KPI cards, a "Needs your attention" list (icon-tile treatment per design feedback — replaces the ringed avatars), and a roster with color-coded contact-status pills. - Empty states handled: no volunteers -> welcome + CTAs; nobody behind -> all caught up. The full volunteer management table remains at /volunteers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Feedback from design review:
- Avatar initials strip honorific prefixes (Mrs./Mr./…) and use first + last name
only, via a shared `avatar_initials` helper (removed the duplicate in the service).
- Top-bar profile menu is triggered by the avatar alone; the name lives in the
dropdown, not the trigger.
- Page header is the page name ("Dashboard") + <title>, not a greeting.
Accessibility (WCAG AA targets):
- Skip-to-content link, landmark labels, aria-labels on icon-only controls,
aria-current on the active nav item, table scope + sr-only caption, visible
focus rings, contrast bumps (slate-400 -> 500/600), role=status/alert on flash.
Responsive:
- Sidebar becomes an off-canvas drawer below lg with an accessible toggle
(button + aria-expanded, Escape/backdrop to close) and a responsive grid/padding.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Names now render as first + last only (no Mr./Mrs./...) wherever a person is shown in the redesign — the shell sidebar/profile menu and the supervisor dashboard (names and aria-labels), plus the avatar initials. - New `NamePresentation.strip_honorific` + a `display_person(user)` view helper. - Presentation-only: the stored `display_name` is left untouched. (An attempted `User#display_name` override was reverted — a security spec requires display_name to round-trip raw input unchanged, e.g. dangerous strings.) - Documented in design.md + CLAUDE.md so it's applied on every page going forward. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The bottom-of-sidebar user block (avatar + name + role) duplicated the identity already shown by the top-right avatar account menu, and was non-interactive. Drop it; the top-right menu is the single source for identity + account actions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "CV" placeholder brand mark wasn't a value-add at 36px (the org name already identifies the chapter) and using the real org logo there would add image/variant infrastructure for little gain. Show the org name alone in the sidebar header; the full logo stays for contexts with room (sign-in, court reports). design.md updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bring the rest of the auth flow in line with the redesigned sign-in: - Forgot password (passwords/new), reset password (passwords/edit), and accept-invitation (invitations/edit) rebuilt in Tailwind, matching the sign-in's inputs/buttons/error styling. CASA's email-or-phone reset is preserved. - Users::PasswordsController uses `layout "casa_auth"`; the invitation-accept action renders in casa_auth (admin invite `new`/`create` untouched). - casa_auth `<title>` is now driven by `content_for(:page_title)`; each page sets its own title (Sign in / Reset your password / Choose a new password / Accept your invitation). Verified all three render 200 in the new layout (accept-invitation via a valid token). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Apply the honorific-free naming rule to the existing Bootstrap UI, not just the redesign: - 37 `.display_name` person-name sites across ~20 legacy views now render via the `formatted_name` helper (org names, form-value symbols, and the checkbox-id slug line were intentionally excluded). - The 4 DataTables (volunteer, supervisor, case-contact, reimbursement) strip honorifics on their Ruby name outputs (display_name + supervisor_name); SQL select/order strings are untouched. - Added a `formatted_name(name)` string helper alongside `display_person`, both backed by `NamePresentation`. Verified /supervisors, /volunteers, /casa_cases render 200 with zero honorifics; datatable classes load clean; standardrb + rendering green. Docs updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The header helper text becomes a short single sentence with no em-dash; the org name already appears in the sidebar header, so it's no longer repeated here. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
giacoelho
requested review from
FireLemons,
compwron and
elasticspoon
as code owners
July 9, 2026 23:01
giacoelho
marked this pull request as draft
July 9, 2026 23:01
DashboardController#show now renders the casadesign supervisor dashboard (SupervisorDashboard + casa_app layout) for supervisors, but the request spec still asserted the old redirect to the volunteers overview and was failing on the branch. Assert the dashboard renders and lists an assigned volunteer instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…app shell First page-by-page migration off the legacy Bootstrap UI, following design.md. The notifications page is linked straight from the new shell (top-bar bell + account menu), so it was the most visible remaining Bootstrap seam. - Render NotificationsController#index in the casa_app layout. - Restyle the index, NotificationComponent, and patch-notes partial onto the design system: card list, leading icon tiles, unread dot, and a cold-start empty state. Switch Font Awesome (fas fa-*) to Bootstrap Icons (bi-*), which is the icon set actually loaded on Tailwind pages. - Move the specs off Bootstrap-class selectors onto semantic data-* hooks (data-notification-list-item, data-read / data-unread-dot) without weakening behavior coverage. All notification/patch-note ordering, counts, and empty states remain asserted. Verified: component, view, request (renders the full casa_app layout), and system specs all pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… backlog - design.md: reframe as the permanent source of truth to refer to for all UI work. Add a "Design decisions (rationale)" log capturing the why behind the system (Tailwind-alongside-Bootstrap, layout opt-in, indigo/slate, Figtree, bi-* icons, icon-tile-vs-avatar, org-name-only sidebar, presentation-only honorific stripping, triage dashboards, empty-state taxonomy, a11y bar, build/output), a repeatable "Migrating a page" playbook, and a migration status that points at the backlog. - design-todo.md: new prioritized, phased backlog (the "what's left") for the page-by-page migration off Bootstrap, plus cross-cutting infra and known pre-existing debt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Second app-shell leaf page (linked from the account menu). Rebuilds the whole edit-profile screen on the design system while preserving every behavior. - Render UsersController in the casa_app layout. - Rewrite edit.html.erb into cards: details, account, security, communication preferences, plus the volunteer languages table — using the documented input, button, and checkbox patterns. - Replace the Bootstrap collapse accordions (Change Password / Change Email) with a small `disclosure` Stimulus controller (real <button> trigger, toggles a hidden panel, syncs aria-expanded) instead of relying on Bootstrap JS. - Add a reusable Tailwind `shared/_form_errors` partial (the legacy `shared/_error_messages` stays for Bootstrap pages); it keeps the `@custom_error_header` hook so "... this password change ..." still renders. - Preserve all field labels, ids, and JS-hook classes (toggle-email/sms-notifications, save-preference, password-new/-confirmation, submit-password, current_password_email, toggle-sms-notification-event) so require_communication_preference.js and password_confirmation.js keep working. Verified: users request spec + the full edit system spec pass (79 examples); erb-lint, standardrb, and StandardJS clean; JS bundle builds. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rofile done Add the two components introduced by the edit-profile migration (the Tailwind form-error summary and the disclosure/collapsible-panel pattern) to design.md, and tick edit profile in both the design.md status and design-todo.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rd subtitle - Add a role badge (current_role as a brand-tinted pill) to the casa_app account menu header, under name + email — the one place identity metadata lives, and a parity with the role line the legacy header used to show. Industry-standard placement for a multi-role internal tool (keeps the sidebar/top-bar chrome clean). - Supervisor dashboard subtitle -> "Track volunteer progress at a glance." - Document the role badge in design.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The person-badge icon added no signal, so remove it. Colour the pill by role using the design system's non-semantic accent tints — Volunteer = sky (blue), Supervisor = violet (purple), Casa Admin = amber — so roles read apart at a glance; unknown roles fall back to slate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pt WCAG AA
- Drop the shouty `uppercase` transform from all design-system labels (account
details, dashboard + languages table headers, patch-note category, auth "or"
divider); render them sentence case. Sentence-case isolated copy ("My
languages", "Learning hours this year").
- Bump muted label text from slate-400 (fails AA) to slate-500/600 for contrast.
- Redesign the form-error summary (shared/_form_errors): a role="alert" card with
a leading icon and a tidy bulleted list instead of the plain block; keeps the
heading text + #error_explanation so specs and behavior are unchanged.
- design.md: add Sentence case + Accessibility (WCAG 2.1 AA) as standing
conventions, and fix the muted-text token. design-todo: track the app-wide
interactive-label sentence-case pass and a slate-400 contrast audit.
Interactive button/field labels shared across pages (Update Profile, Change
Password, field labels) are spec-coupled across users/volunteers/all-casa-admin
and a shared example, so they're deferred to a holistic pass (tracked).
Verified: 109 examples across the touched pages pass; erb-lint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per design feedback: one sentence, no bullets, dashboard-style icon tile. - shared/_form_errors now renders "Unable to save: <messages via to_sentence>" inside a rose danger icon tile (bg-rose-100 text-rose-600, bi-exclamation- triangle) matching the dashboard, dropping the old heading + bulleted list. - Update edit_spec assertions from the Rails "N errors prohibited this X from being saved:" text to "Unable to save". - design.md: refresh the Form errors component note. Verified: 79 users edit + request examples pass; erb-lint clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ount/description/Delete Per review: go back to a divider between expenses (border-t border-slate-200 + symmetric py-4, so Delete no longer sits flush against the next divider), restore the visible field labels, and stack the fields vertically — amount, then description (required), then a right-aligned Delete below — instead of the inline bordered-card layout. All JS hooks (.nested-form-wrapper, .expense-*-input, .remove-expense-button, data-*, hidden id/_destroy) preserved. Verified: additional_expenses system spec (9) green; erb_lint clean. design.md note updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t a ghost-button row Replace the full-width danger_outline "Delete" ghost button (its own row below the fields, which pushed content down and added whitespace) with a compact trash icon button pinned to the top-right of each expense row (flex items-start, h-8 w-8, rose hover). Fields stay stacked with visible labels in the left column. Specs click the .remove-expense-button hook (the button is icon-only now). The saved-expense confirm dialog is unchanged. Verified: expense remove / cancel / add system specs green (in isolation; the full-file run has the known Chrome-under-load flake on an unrelated add example); erb_lint clean. design.md updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…it" flash link
Replace the floating "Create another" mode checkbox with a secondary "Submit & add another" button
beside the primary Submit (the Django-style "save and add another"). The primary stays first in the
DOM so Enter submits normally and click_on "Submit" (Capybara :smart) still resolves it; the
secondary carries metadata[create_another], reusing the existing finish_editing redirect and the
used_create_another analytics scope unchanged.
Since there's no per-contact show page, the create-another success flash now carries a trusted
action link -- flash[:notice_action] = {label, path}, rendered by the casa_app flash partial as a
link_to inside the notice (not raw HTML; the :json cookie serializer drops html_safe) -- pointing
at the case-contacts list so the user can find what they just created.
Verified: new_spec (plain Submit still works + new Submit & add another with the flash link),
request form_spec (53, incl. create_another) green; standardrb + erb_lint clean. design.md updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… WCAG axe coverage - Notes helper text spells out the interaction: "Check each topic you discussed, then write your notes in the field that appears. Unchecking a topic removes it and its notes." - Structured-address columns rebalanced across all three forms (reimbursement + both profiles): city 1/2 / state 1/3 / ZIP 1/6 (was state 1/6 / ZIP 1/3) -- state is wider than ZIP since a state name is longer than a ZIP. Miles-driven keeps its narrow max-w (short numeric entry). - Added an axe (WCAG 2 A/AA) system spec for the whole case-contact form (details/notes/reimbursement/expenses, reimbursement revealed) -- passes clean. Verified: axe spec green; erb_lint clean. design.md updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dth expense rows - Structured-address fields (reimbursement + both profiles) now have persistent VISIBLE labels (line 1/2 wrapped in their own groups, city/state/zip labeled) instead of relying on placeholders that vanish on input; line 2 keeps an "Apartment, suite, unit" hint. Redundant placeholders dropped. - "Total miles driven" moved into a half-width sm:grid-cols-2 cell, aligning with the details section's 2-col grid rhythm (was an arbitrary max-width). - Additional-expense rows: inputs are now FULL WIDTH with a small "Remove" text action on the amount label's line (top-right) -- no side icon narrowing the fields, no bottom ghost-button row. Expenses are free-form, so the notes check-to-add pattern doesn't apply. Verified: axe (WCAG 2 A/AA) still clean; expense add/remove/cancel, volunteer-address, and new-contact reimbursement system specs green; erb_lint clean. design.md updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… the mb-1.5 token Measured the expense-row gaps in a real browser: label->input 6px, field groups 16px, row py-4 -- already identical to the canonical labeled field (miles) and the label_class token. This tidies the amount section to express that 6px gap with mb-1.5 on the header line (the label_class token) rather than mt-1.5 on the input, so the code uses the token instead of re-deriving it. Render-identical. Verified: expense add/remove system specs green; erb_lint clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…and "Add another expense" Measured the details-input -> Add-button gap at 48px. Two causes: (1) the row's space-y-4, with the trailing hidden id/_destroy inputs, put a 16px bottom margin on the description group (space-y margins the non-last child) on top of the row's py-4; (2) the button wrapper added another mt-4. Space the two groups with mt-4 on the description instead of space-y-4 on the row, and drop the button's mt-4 -- the button now sits at the row's py-4. Measured 48px -> 16px. Verified: expense add/remove/cancel system specs green; erb_lint clean. design.md updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an alert_classes(:success|:warning|:danger|:info) + alert_icon token (design_system_helper, sibling of button_classes): the shared design-system alert card (rounded-lg border + leading severity icon), full class strings written out for the Tailwind scanner. - Flash banners: extract shared/_flashes, rendered by EVERY layout (casa_app, casa_auth, all_casa_admin) via render "shared/flashes", wrapper_class:. Replaces three drifted inline copies (casa_app had notice_action + alert/type classes; casa_auth differed; all_casa_admin dropped the type classes). notice -> success, else -> warning; keeps the alert/<type> classes, header-flash container, a11y role, and the notice_action link. - Form errors: shared/_form_errors now uses the SAME alert card (danger variant) so the summary matches the flash banners. It's the only error summary now -- deleted the legacy bulleted shared/_error_messages and the bridge casa_admins/_errors and migrated their all-casa / casa-admin pages onto _form_errors (id="error_explanation" + alert class kept; "Unable to save:" + to_sentence). - Specs: the four "N errors prohibited..." assertions -> "Unable to save" (the specific messages are still shown via to_sentence). Verified: axe (WCAG) on casa_app/casa_auth + the form; all_casa_admin, casa_admins, password_change, volunteers/invite (.notice + .header-flash), users/edit + supervisors (#error_explanation), and the casa_org request -- all green; standardrb + erb_lint clean. design.md updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… errors (novalidate) The form had no `novalidate`, so the browser's native HTML5 validation (required / type / min) fired its own bubble and blocked the submit -- the design-system treatment (.field_with_errors rose borders + the _form_errors summary, both already styled) never got to render. Add novalidate: true so an invalid submit reaches the server and re-renders with the design-system validation. Native bubbles can't be styled, so this is the only way to align field-level validation. Also fix four stale specs that still checked the removed "Create another" checkbox (from the earlier two-button change) -> click "Submit & add another". (Missed before because I re-ran only a subset; the full new_spec run here surfaced them.) Verified: new (29), edit + additional_expenses (17), contact_topic_answers system specs green; erb_lint clean. design.md documents the novalidate requirement. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ign-system errors show Extend the case-contact fix to every form. A global handler in application.js sets form.noValidate on all forms on load / turbo:load / turbo:frame-load (Turbo Drive is off, so it runs on each full page load), with a data-native-validation opt-out. So on any of the ~24 forms with required/type/min fields, an invalid submit now reaches the server and renders the design-system validation (.field_with_errors rose borders + the _form_errors summary) instead of the browser's unstyleable native bubbles. Verified across areas: case contacts (new/edit/expenses), casa_cases, court_dates, supervisors, learning_hours new specs -- all green; standardjs clean. design.md updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…case-contact form
The banner was a run-on to_sentence with mixed casing ("Contact Type"), a doubled period
("reimbursement., and"), and only the date field got a red border -- color alone, and not every
error was surfaced at its field.
Field level: new field_error / field_error_attrs helpers render a rose message + icon under each
invalid field and tie it to the input via aria-invalid + aria-describedby (WCAG 1.4.1 -- never color
alone). Wired onto the case-contact date, contact-types, medium, duration, relevant-cases and miles
fields; the rose border now also keys off [aria-invalid]. Attribute errors show at the field;
cross-field :base errors (the reimbursement rules) stay in the summary.
Summary: shared/_form_errors now lists every error (a lone error inline, several as a list) instead
of a run-on sentence, so it matches the per-field messages with no doubled punctuation. Keeps
id=error_explanation / role=alert / the alert class / "Unable to save" (spec hooks). Shared by ~31
forms.
Message copy: validation labels are sentence case (Contact type, CASA case, Other expense
amount/details -- CASA acronym kept) and the :base sentences drop their trailing period.
Verified: case_contact model (76), case_contacts new/edit/additional_expenses/form-request,
users/edit, supervisors, error_explanation-hook specs, axe -- all green; a throwaway DOM check
confirmed aria-invalid resolves to visible messages. standardrb + erb_lint clean; design.md updated.
Known follow-up: the relevant-cases multiselect (Form::MultipleSelectComponent) shows a visible
message but not aria-invalid, since the component takes no html passthrough.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tes) Mirrors the existing .aider* ignore -- keeps a personal, un-shared local memory file out of the tree (and safe from `git clean -fd`) so working notes never land in the public project CLAUDE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ation Five fixes from review of the record-new-case-contact form: - Banner lists errors in form order, not model/validation order. shared/_form_errors takes an optional `order:` (attributes top-to-bottom); the case-contact form passes its field order, and unlisted attributes + :base fall to the end. - Field-level message is now secondary-gray text (text-slate-500) with a centered rose icon (was rose text, top-aligned) -- the field border + icon carry the error color, the text stays readable. - Field-level message renders consistently BELOW each field/group: the duration error moved out of the minutes grid-column to below the whole duration fieldset (marked on the fieldset, like medium). - Miles driven is now flagged. It defaults to 0 (a valid number), so the reimbursement rule -- moved from :base to :miles_driven -- now marks the field + shows its message, not just the banner. - Other expense details is now flagged: field_error added to the (casadesign-only) expense row partial, with a per-row unique id via form.field_id. Verified: case_contact model (76), case_contacts new/edit/additional_expenses, users/edit, axe -- all green; a :js DOM dump confirmed form-order banner + every invalid field flagged below itself. standardrb + erb_lint clean; design.md updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rrors; softer Remove - Expenses are now a required pair: an amount requires a description (already the rule) AND a description now requires an amount. So a started expense must be completed or the row left blank -- a brand-new blank row still saves, so "Add another expense" can create it, then fill. The amount label gets the * and its own field_error; description was already wired. - Field-level validation messages are sentence case (field_error upcase_first): "Can't be blank", not "can't be blank". (The banner was already capitalized via the attribute name.) - The expense "Remove" action uses the documented tertiary ghost (slate) instead of rose text, which read as jarring. Verified: additional_expense model (+ new amount-requires-description spec), additional_expenses request + system, case_contacts new + form-request, expense policy -- all green; a :js dump confirmed a description-only row flags the amount, messages are capitalized, and Remove renders slate. standardrb + erb_lint clean; design.md updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…'t drop it Blank/partial expense rows should block submit (fill both fields or Remove the row), not be silently dropped -- a volunteer may have just forgotten to finish one. An AdditionalExpense now requires a positive amount AND a description, enforced only once the contact is being submitted (active_or_details?): the blank row "Add another expense" creates up front and draft autosaves are not blocked, and the standalone create still allows the blank add. On submit, an incomplete or empty row is flagged on both fields and blocks the form. (reject_if: :all_blank stays: it keeps autosave from creating duplicate blank rows before the create-on-add wires the record id; a persisted blank row still validates and flags on submit.) Verified: additional_expense model (rewritten for the new rule + empty-row-flags-both case), additional_expenses request + system, case_contacts new / edit / form-request -- all green; a :js run confirmed a blank expense blocks submit with both fields flagged "Can't be blank" and listed in the banner. standardrb clean; design.md updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e-form required-field audit The reimbursement mailing address was the one required section with no field-level treatment -- its rule was a :base error, so it showed only in the summary, never on the fields. Move the two address rules (blank / invalid) from :base to the :volunteer_address attribute and label it "Mailing address", then wire field_error + aria onto the address fieldset's required parts (line 1, city, state, zip). A blank address now flags each part (rose border + aria), shows "Must be entered for reimbursement" under the fieldset, and lists "Mailing address must be entered for reimbursement" in the banner (ordered right after miles). Audited the whole form by submitting with every required field blank: relevant cases, contact date, contact type(s), contact medium, miles driven, mailing address, and both expense fields all flag at the field AND in the banner. Verified: case_contact model (76), case_contacts new (29) / edit / additional_expenses / form-request -- all green; a :js audit confirmed every required field flags. standardrb + erb_lint clean; en.yml valid; design.md updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pace-y vs m-0)
Tailwind v4 emits space-y-* as `:where(& > :not(:last-child)) { margin-block-end }` -- zero
specificity -- so the fieldsets' `.m-0` (0,1,0) overrode it and collapsed the gap to 0px after the
contact-medium and duration fieldsets (measured). Reset only the inline margin (`mx-0`) so space-y
still owns the block gap. Measured: medium -> duration now 24px (was 0), matching the other sections.
design.md documents the gotcha.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…al field_error_proc) Roll the field-level treatment out app-wide via Rails' global field_error_proc rather than editing each form. It keeps the .field_with_errors rose border and, for any text-like control (input / select / textarea) whose attribute is invalid, adds aria-invalid + the same secondary-gray message + rose icon the field_error helper produces. It SKIPS a control that already carries aria-describedby (hand-placed via the helper -- the case-contact fieldsets), so nothing doubles; radios / checkboxes / hidden inputs are wrapped for the border but left to their group's field_error / the summary banner. New forms get it automatically. Verified: proc adds the message on casa_cases/new (case_number, with aria-invalid + aria-describedby); no doubles on the case-contact form (7 errors, 7 messages, 0 duplicate ids); Nokogiri preserves the field HTML. No breakage across case contacts, casa_cases, court_dates, supervisors, learning_hours, users/edit, sessions (devise), all_casa_admins, devise passwords, and the case_contact model + form-request specs. standardrb clean; design.md documents it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…volunteer cases) When a case has multiple volunteers and the editor isn't one of them, the app used to disable the reimbursement address fields (it couldn't infer whose address to save). Now the form offers a "Reimburse which volunteer?" dropdown of the case's volunteers; picking one prefills the address fields from that volunteer's saved address (JS) and saves the entered address back to that volunteer. - Model: virtual reimbursement_volunteer_id; reimbursement_volunteer_options; volunteer resolves to the chosen one; address_field_disabled? true only when there's genuinely no volunteer; validate a choice is made when reimbursement is wanted. - Controller/params: permit reimbursement_volunteer_id; save the address to the resolved volunteer. - View: the picker (native select + chevron) above the now-enabled address fields, with a field error; the disabled path + reworded message now only cover the no-volunteer case. - JS: case-contact-form#pickReimbursementVolunteer prefills the address from the selected option. Verified: case_contact model (78, incl. new picker/validation cases), decorator (27), form-request (53), case_contacts edit / new / additional_expenses / new_design, reimbursements request, and a new end-to-end spec (admin picks a volunteer -> address saves to that volunteer). standardrb / standardjs / erb_lint clean; design.md documents it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sible, shared helper)
The medium icon on each case-contact card was a bare aria-hidden <i> -- unlabeled (a mystery to
sighted users) and invisible to screen readers. Add a shared contact_medium_badge(case_contact)
helper that renders the brand-tinted badge with the medium name (CaseContactDecorator#medium_label)
as both a native title tooltip and the accessible name (role=img + aria-label), with the inner <i>
aria-hidden. The card renders it via the helper now; reuse it wherever the medium icon appears.
Also fixes two stale card view-spec assertions ("Some question:" / "Additional notes:") left over
from the earlier dt/dd (colon-less) card redesign, and adds a badge spec.
Verified: card view (12), decorator (27), case_contacts index (19), axe (14) -- all green; standardrb
+ erb_lint clean; design.md documents the badge.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nly, not Voice Only)
The medium icon's tooltip + accessible label now read in sentence case per the design system
("voice-only" -> "Voice only", "text/email" -> "Text/email"), via tr("-", " ").humanize instead of
titleize. Only the badge (contact_medium_badge) uses medium_label, so this doesn't touch the form's
medium dropdown/radios (still titleized -- a separate, broader change if we want full alignment).
Verified: decorator (33, incl. new medium_label cases), card view (12) -- green; standardrb clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…WCAG check
Full sweep so the medium reads the same everywhere. contact_mediums (the form's medium dropdown +
radios, and the index filter) and the new-design table/detail display now use
medium_type.tr("-", " ").humanize -- matching CaseContactDecorator#medium_label (the badge) -- so
"In Person" / "Voice Only" / "Text/Email" become "In person" / "Voice only" / "Text/email"
everywhere. Specs + the shared fill_in_case_contact_fields helper updated to the new labels.
WCAG (the medium badge): the icon has a text alternative via aria-label (role=img), so meaning isn't
conveyed by color or shape alone (1.1.1 / 1.4.1 / 4.1.2), and brand-600 on brand-50 is 5.6:1 (passes
1.4.11 non-text contrast). axe is clean on the index + form.
Verified: case_contacts new (29) / edit (8) / index (19) / new_design (22) / topic_answers (11),
decorator (33), card view (12), axe (14) -- all green; standardrb + erb_lint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…JS test fixture Cosmetic consistency with the app-wide sweep -- the fixture's radio labels: "In Person" -> "In person", "Text/Email" -> "Text/email", "Voice Only" -> "Voice only". The test filters by element id, not label text, so behavior is unchanged (jest 13/13). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… vs card mb-1) The _case_contact card root carried `mb-1` (4px, specificity 0,1,0), overriding the parent stack's zero-specificity `space-y-4` (:where(...)) on both the index and drafts -- collapsing the gap between cards in a case from the intended 16px to 4px (pixel-measured: 16 fixed vs 4 buggy). Drop the card's bottom margin so the stack owns the gap; `.container-fluid` stays the inert spec hook (draft-card assertions now match `.container-fluid`, not `.container-fluid.mb-1`). Documented in design.md next to the fieldset/m-0 instance of the same space-y trap. Card view + index system specs green (31 examples). The medium-badge tooltip (native `title` + aria-label) was intact in HEAD -- it had only vanished from a stale working-tree revert, now reconciled. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ve title + the hover-media gate) The medium badge relied on a native `title` tooltip, which is easy to miss (browser delay, only on the 32px glyph, never on touch) -- reported twice. Replace it with a visible label bubble that reveals on hover, keeping `role=img` + `aria-label` as the screen-reader name (the visible label is aria-hidden, so AT isn't double-announced). Crucially it is NOT built on Tailwind's `group-hover:*`: v4 gates `hover:`/`group-hover:` behind `@media (hover: hover)`, so the tooltip would vanish on hybrid / touch-screen laptops driven by a mouse (they report `hover: none`) -- a likely reason it "couldn't be seen". A hand-written, un-gated `.badge-tip` rule (`.group:hover > .badge-tip`) shows on any hovering pointer. Pixel-verified in headless Chrome (forcing :hover): opacity 0 -> 1, and the label is not clipped by the card's overflow-hidden on tall or draft-height cards. Card view + index system specs green (31). design.md documents the pattern + the hover-media-gate gotcha. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ne, drop the icon Reviewer feedback: the medium icon + hover tooltip wasn't identifiable as the means of contact. The medium IS meaningful (in person vs text matters for advocacy), but an icon is a poor signifier -- the glyphs are ambiguous (envelope = text or letter?), the meaning hid behind a hover, and it's invisible on touch. Move the medium into the card's existing meta line (CaseContactDecorator#subheading) as plain text -- "July 21, 2026 | In person | 30 minutes" -- a peer to date/duration/miles, omitted when unset. This improves BOTH cards that render subheading: the contacts index card and the case-show card. Remove the icon from both cards, plus the now-unused contact_medium_badge helper and .badge-tip CSS. design.md's medium section is rewritten to the meta-line pattern (with a note to never reintroduce an icon-alone or hover-gated reveal). decorator + card view specs (45) and index + casa_cases show system specs (58) green; erb_lint + standardrb clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…kbox column
The bulk-assignment table's header checkbox was a bare, unlabeled box (aria-label only), so "select
all" wasn't discoverable, and its <th> lacked align-top -- it floated centered in the tall (wrapped)
header while the text headers top-aligned, reading as misaligned.
- Wrap the select-all in a visible "Select all" <label> (discoverable + labeled; the wrapping label is
the accessible name, so the redundant aria-label is dropped).
- Add align-top to the header checkbox <th> and the per-row checkbox <td> so they line up with the
header text / row name (the checkbox now sits within 2px of the Name header, was floating centered).
Per-row checkboxes keep their "Select {name}" aria-label (the row name is the visible context).
Verified: select-all still checks all rows and reveals "Manage volunteers (N)"; volunteers index
system spec 23/0; erb_lint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…box + align to the text line Follow-up to reviewer feedback: the visible "Select all" text widened the narrow checkbox column and its color didn't match the other headers. Industry standard for a bulk table (Gmail, GitHub, Linear, MUI/AntD) is a bare header checkbox with an accessible name, not visible column-header text -- so revert to a bare checkbox with aria-label + title "Select all". Column back to 40px, no color mismatch. Also fix vertical alignment (all measured to 0px offset in headless Chrome): - mt-0.5 drops the header + row checkboxes onto the text line (checkbox center == the name's first-line center on every row; header checkbox center == the Name header center). - align-top on the actions cell so Edit/Impersonate top-align with the row's first line instead of floating centered in tall (wrapped) rows. Volunteers index system spec 23/0; erb_lint clean; select-all + "Manage volunteers (N)" still work. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-wide (+ document the pattern) The cases-index Edit button floated vertically centered while the data cells top-aligned -- a hardcoded action <td> missing align-top. Same drift existed across supervisors / casa_admins / other_duties / org-settings (10 casa_org tables) / hearing-types, and the dashboard tables were middle-aligned throughout. App-wide audit + fix: - align-top on every trailing action <td> so Edit/Delete/View top-align with the row's first line instead of floating centered when a neighbouring cell wraps to two lines. - dashboard (volunteer + supervisor) tables had no align-top at all (tall status cells) -- align-top on all their body cells. Checkboxes already follow the pattern: a bulk-select alone in a cell uses align-top + mt-0.5 to sit on the adjacent column's first text line (volunteers); a checkbox with its label in the same cell uses inline-flex items-center (self-aligning). Documented the full row-alignment pattern in design.md's Tables section (body/action cells, checkbox-alone vs label-in-cell, bare select-all header). Verified: cases Edit button top-aligns to the case-number line (headless, topDiff -1); casa_cases / casa_admins / supervisors index specs 17/0; erb_lint clean on all 17 views. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e so it top-aligns Missed in the table-alignment audit: the "Mark complete" toggle wrapped to two lines in the narrow reimbursement-queue column, and its `inline-flex items-center` label then centered the checkbox across both wrapped lines -- pushing the whole control ~10px below the row's first line (measured). A checkbox-with-label cell is NOT automatically aligned if the label wraps. whitespace-nowrap keeps "Mark complete" on one line: the toggle now top-aligns with the row (measured diff 0, was +10), the label is one line, and there is no horizontal overflow (scrollW 1265 < 1280). design.md's table row-alignment pattern updated with the wrap caveat. Reimbursement system spec 3/0; erb_lint clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is a work in progress. Will flag when it is ready to merge.